// CyTargetLinker RIN conversion,
//
// scripts to create RIN network files for several online databases
// can be used with the CyTargetLinker app for Cytoscape
//
// Copyright 2011-2013 Department of Bioinformatics - BiGCaT, Maastricht University
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package cytargetlinker.conversion;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import org.bridgedb.DataSource;
import org.bridgedb.IDMapperException;
import org.bridgedb.IDMapperStack;
import org.bridgedb.Xref;

import cytargetlinker.conversion.ArgsParser.AFilesAttributes;
import cytargetlinker.conversion.ArgsParser.AFilesIn;
import cytargetlinker.conversion.ArgsParser.AFilesOut;
import cytargetlinker.conversion.ArgsParser.AHelp;
import cytargetlinker.conversion.ArgsParser.GraphBuilder;
import cytargetlinker.conversion.graph.Graph;
import cytargetlinker.conversion.graph.Graph.Edge;
import cytargetlinker.conversion.graph.Graph.Node;

/**
 * Converts predicted miRNA targets from miRDB to a XGMML network.
 * 
 * The input files for this script can be downloaded here:
 * http://mirdb.org/miRDB/download/MirTarget2_v4.0_prediction_result.txt.gz
 * 
 * @author martina
 */
public class MiRDBParser {

	private final static Logger log = Logger.getLogger(MiRecords.class.getName());
	private static Args pargs;
	private interface Args extends AHelp, AFilesIn, AFilesOut, AFilesAttributes {}
	
	/**
	 * USAGE: java -cp conversion.jar cytargetlinker.conversion.MiRDBParser
	 * 
	 * REQUIRED ARGUMENTS:
	 * [-i] = input file path
	 * [-o] = output file path
	 * [-m] = list of bridgedb files for identifier mapping
	 * [-l] = log file path
	 * [--organism] = scientific name for the organism
	 *  
	 * OPTIONAL ARGUMENTS:
	 * [-n] = network name
	 * 
	 */
	public static void main(String argv[]) throws Exception {
		pargs = ArgsParser.parse(argv, Args.class);
		MiRDBParser converter = new MiRDBParser();
		converter.startConversion();
	}
	
	private IDMapperStack idMapper;
	private Graph graph;
	private Map<String, List<String>> edges;
	private List<String> foundConnections;
	
	private int countEdges = 0;
	private int countMiRNAs = 0;
	private int countGenes = 0;
	private boolean mapping = false;

	public MiRDBParser() throws Exception {
		edges = new HashMap<String, List<String>>();
		foundConnections = new ArrayList<String>();
	}
	
	private void startConversion() throws Exception {
		File in = pargs.getInput();
		if(in != null) {
			Utils.setUpLogger(log, getLogFile(), false);
			
			idMapper = Utils.initIDMapper(pargs, false);
			if(idMapper != null) {
				mapping = true;
				log.info("identifier mapping on");
			}
			
			if(pargs.isOrganism()) {
				log.info("Conversion of miRDB started ...\n");
				
				ArgsParser.convertAndWrite(pargs, pargs, new GraphBuilder() {
					public Graph buildGraph(File in) throws Exception {
						return convert(in);
					}
				});
					
				log.info("miRDB network file created.\n\n");
			} else {
				System.out.println("Please specify organism!");
			}
			
		} else {
			log.severe("Please specify input file, downloaded from http://mirdb.org/miRDB/download.html!");
		}
	}
	
	private Graph convert(File in) throws IOException {
		graph = new Graph();
		
		BufferedReader br = new BufferedReader(new FileReader(in));

		setNetworkAttributes(in);
		
		String line = null;
		List<String[]> rows = new ArrayList<String[]>();
		while ((line = br.readLine()) != null) {
			String[] str = line.split("\t");
			if (str[0].contains(SpeciesCodes.getCode(pargs.getOrganism()))) {
				rows.add(line.split("\t"));
			}
		}
	
		for(String[] r : rows) {
			String geneNode = createGeneNode(r);
			if(geneNode != null) {
				String miRNANode = createMiRNANode(r);
				addEdge(geneNode, miRNANode, r);
			}
		}
		
		log.info(foundConnections.size() + " interactions have been found.\n" + countGenes + " gene nodes.\n" + countMiRNAs + " miRNA nodes.\n");
		
		return graph;
	}
	
	private String createGeneNode(String[] r) {
	
		String geneName = r[1];
		String geneId = r[1];
		String type = "gene";
		String organism = pargs.getOrganism();
		
		if(graph.getNode(geneId) == null) {
			String ensembl = "";
			String entrez = "";
			String identifiers = "[" + geneId;
			
			if(mapping) {
				Xref xrefIn = new Xref(geneId, DataSource.getBySystemCode("Q"));
				try {
					Set<Xref> ens = idMapper.mapID(xrefIn, DataSource.getBySystemCode("En"));
					if(!ens.isEmpty()) {
						ensembl = ens.iterator().next().getId();
						identifiers = identifiers + "," + ensembl;
					}
					Set<Xref> en = idMapper.mapID(xrefIn, DataSource.getBySystemCode("L"));
					if(!en.isEmpty()) {
						entrez = en.iterator().next().getId();
						identifiers = identifiers + "," + entrez;
					}
				} catch (IDMapperException e) {
					// could not be mapped
				}
				
			}
			identifiers = identifiers + "]";
			
			if(!ensembl.equals("")) {
				Node node = graph.addNode(geneId);
				node.appendAttribute("identifiers", identifiers);
				node.appendAttribute("label", geneName);
				node.appendAttribute("biologicalType", type);
				node.appendAttribute("organism", organism);
				node.appendAttribute("RefSeq", geneId);
				node.appendAttribute("ensemblID", ensembl);
				node.appendAttribute("entrezGeneID", entrez);
				countGenes++;
			} else {
				return null;
			}
		}
		
		return geneId;
	}
	
	private String createMiRNANode(String[] r) {
		String miRNA = r[0];
		String type = "microRNA";
		String organism = pargs.getOrganism();
		
		if(graph.getNode(miRNA) == null) {
			String mimat = "";
			String identifiers = "[" + miRNA;
			Xref xrefIn = new Xref(miRNA, DataSource.getBySystemCode("Mb"));
			
			if(mapping) {
				try {
					Set<Xref> result = idMapper.mapID(xrefIn, DataSource.getBySystemCode("Mb"));
					Set<Xref> result2 = idMapper.mapID(xrefIn, DataSource.getBySystemCode("Mbm"));
					
					List<String> list = new ArrayList<String>();
					list.add(miRNA);
					for(Xref x : result) {
						if(!list.contains(x.getId())) {
							list.add(x.getId());
							identifiers = identifiers + "," + x.getId();
						}
					}
					for(Xref x : result2) {
						if(!list.contains(x.getId())) {
							list.add(x.getId());
							if(x.getId().startsWith("MIMAT")) {
								mimat = x.getId();
							}
							identifiers = identifiers + "," + x.getId();
						}
					}
				} catch (IDMapperException e) {
					// could not be mapped
				}
			}
			identifiers = identifiers + "]";
			
			Node node = graph.addNode(miRNA);
			node.appendAttribute("identifiers", identifiers);
			node.appendAttribute("label", miRNA);
			node.appendAttribute("miRBaseAccession", mimat);
			node.appendAttribute("biologicalType", type);
			node.appendAttribute("organism", organism);
			countMiRNAs++;
		}
		
		return miRNA;
	}

	private void addEdge(String gene, String mirna, String[] r) {
		if (edges.containsKey(gene)) {
			if (!edges.get(gene).contains(mirna)) {
				Edge e = graph.addEdge("" + countEdges, graph.getNode(mirna), graph.getNode(gene));
				setEdgeAttributes(e, r[2]);
				edges.get(gene).add(mirna);
				foundConnections.add(gene + "\t" + mirna);
				countEdges++;
			}
		} else {
			Edge e = graph.addEdge("" + countEdges, graph.getNode(mirna), graph.getNode(gene));
			setEdgeAttributes(e, r[2]);
			
			List<String> list = new ArrayList<String>();
			list.add(mirna);
			foundConnections.add(gene + "\t" + mirna);
			edges.put(gene, list);
			countEdges++;
		}
	}
	
	private void setEdgeAttributes(Edge e, String score) {
		try {
			Double d = Double.parseDouble(score);
			e.setAttribute("score", d.toString());
		} catch(NumberFormatException ex) {
			e.setAttribute("score", "");
		}
	}
	
	private void setNetworkAttributes(File input) {
		graph.setTitle("miRDB (" + pargs.getOrganism() + ")");
		graph.setAttribute(CommonAttributes.DATABASE.getName(), "miRDB (" + pargs.getOrganism() + ")");
		graph.setAttribute(CommonAttributes.TYPE.getName(), "MTIs");
		graph.setAttribute(CommonAttributes.IDENTIFIERS.getName(), "identifiers");
		graph.setAttribute(CommonAttributes.SOURCE_FILE.getName(), input.getName());
		graph.setAttribute(CommonAttributes.TARGET_DATASOURCE.getName(), "RefSeq");
		graph.setAttribute(CommonAttributes.SOURCE_DATASOURCE.getName(), "miRBase");
		graph.setAttribute(CommonAttributes.SOURCE_TYPE.getName(), "mirna");
		graph.setAttribute(CommonAttributes.TARGET_TYPE.getName(), "gene");
	}
	
	private File getLogFile() {
		if(pargs.isLogFile()) {
			return pargs.getLogFile();
		} else {
			if(pargs.isInput()) {
				return new File(pargs.getInput() + ".log");
			} else {
				return null;
			}
		}
	}
}
